package world

import (
	"MetroBuilderGameJam/gameEngine/geom"
)

type TrainStage int

const (
	TrainStageInDrive TrainStage = iota
	TrainStageUnload
	TrainStageLoad
)

func NewTrain() *Train {
	t := &Train{
		togglePassengerDuration: 0.6,

		MaxSpeed: 60,
		capacity: 6,
		stage:    TrainStageInDrive,
	}
	t.passengers = make([]*Passenger, 0, t.capacity)

	return t
}

type Train struct {
	Position         *geom.Point
	Rotation         geom.Radian
	DirectionTo      *Station
	CurrentGoal      *geom.Point
	Speed            float64
	MaxSpeed         float64
	CurrentBranch    *BranchCompleted
	RememberedBranch *BranchRemembered
	StationBoarding  *Station
	RelocationPlan   *TrainPlan

	stage                   TrainStage
	passengers              []*Passenger
	capacity                int
	togglePassengerElapsed  float64
	togglePassengerDuration float64
	passengerDelivered      int
}

func (t *Train) PlaceOnBranch(branch *BranchCompleted, directionTo *Station, curPosition *geom.Point) {
	if branch == nil || directionTo == nil || curPosition == nil {
		return
	}
	t.CurrentBranch = branch
	t.RememberedBranch = NewRememberedBranch(branch)
	t.DirectionTo = directionTo
	t.Position = curPosition
	if curPosition == directionTo.Position() {
		t.ArriveToStation(directionTo)
	} else {
		t.Speed = t.MaxSpeed
	}
}

func (t *Train) ArriveToStation(station *Station) {
	t.StationBoarding = station
	t.Speed = 0
	if t.CurrentBranch.RailRoad().Count() == 0 {
		t.RemoveFromRoad()
		return
	}

	nextBranch := t.CurrentBranch.RailRoad().GetAnotherCompletedBranchForStation(station, t.CurrentBranch)
	if nextBranch != nil {
		t.CurrentBranch = nextBranch
	}
	if nextBranch != nil || t.CurrentBranch.RailRoad().Count() == 1 {
		t.RememberedBranch = NewRememberedBranch(t.CurrentBranch)
	}
	if len(t.RememberedBranch.Additional) > 0 && t.RememberedBranch.Additional[0].HasStation(station) {
		path := t.RememberedBranch.Additional[1:]
		t.RememberedBranch = t.RememberedBranch.Additional[0]
		for i := 0; i < len(path); i++ {
			t.RememberedBranch.AddNextBranch(path[i])
		}
	}
	t.directToNextStation()
}

func (t *Train) directToNextStation() {
	if t.DirectionTo == t.RememberedBranch.FromStation() {
		t.DirectionTo = t.RememberedBranch.ToStation()
	} else {
		t.DirectionTo = t.RememberedBranch.FromStation()
	}
}

func (t *Train) Update(dt float64) {
	if t.CurrentBranch == nil {
		if t.RelocationPlan != nil && t.RelocationPlan.IsPlaced {
			t.Relocate()
		}
		return
	}

	if t.IsOnStation() && t.PassengersProcess(dt) {
		return
	}
	if t.IsOnStation() && t.RelocationPlan != nil && t.RelocationPlan.IsPlaced {
		t.Relocate()
	}

	t.Speed = (t.MaxSpeed + t.Speed) / 2

	t.Move(dt)
}

func (t *Train) PassengersProcess(dt float64) bool {
	if t.CurrentBranch == nil || t.DirectionTo == nil || t.Position == nil || t.StationBoarding == nil {
		return false
	}
	if t.stage == TrainStageInDrive {
		t.stage = TrainStageUnload
	}
	if t.stage == TrainStageUnload && len(t.passengers) > 0 {
		if t.unloadPassenger(dt) {
			return true
		}
	}
	t.stage = TrainStageLoad

	if t.stage == TrainStageLoad && len(t.passengers) < t.capacity && len(t.StationBoarding.Passengers()) > 0 {
		if t.loadPassenger(dt) {
			return true
		}
	}
	t.stage = TrainStageInDrive

	return false
}

func (t *Train) Passengers() []*Passenger {
	return t.passengers
}

func (t *Train) Move(dt float64) {
	if t.CurrentBranch == nil || t.DirectionTo == nil || t.Position == nil {
		return
	}
	t.StationBoarding = nil
	bendPoint := t.RememberedBranch.CalcBendPosition()
	t.CurrentGoal = chooseMovingGoal(t.Position, t.DirectionTo.Position(), &bendPoint)

	moveDistance := t.Speed * dt
	distToGoal := t.Position.DistanceTo(*t.CurrentGoal)
	if distToGoal < moveDistance {
		newPos := t.CurrentGoal.AsVec2().AsPoint()
		t.Position = &newPos
		moveDistance -= distToGoal
		t.CurrentGoal = t.DirectionTo.Position()
	}
	if t.Position.DistanceTo(*t.CurrentGoal) > 0 {
		t.Rotation = t.Position.AngleTo(*t.CurrentGoal)
	}
	if t.Position.DistanceTo(*t.DirectionTo.Position()) == 0 {
		t.ArriveToStation(t.DirectionTo)
		return
	}
	dir := t.CurrentGoal.AsVec2().Sub(t.Position.AsVec2()).Normalize()
	p := t.Position.AsVec2().Add(dir.Mul(moveDistance)).AsPoint()
	t.Position = &p
}

func chooseMovingGoal(position, finalPosition, middlePosition *geom.Point) *geom.Point {
	distToStation := position.DistanceTo(*finalPosition)
	distToStationFromMiddle := middlePosition.DistanceTo(*finalPosition)
	if distToStation > distToStationFromMiddle {
		return middlePosition
	}
	return finalPosition
}

func (t *Train) RemoveFromRoad() {
	t.CurrentBranch = nil
	t.DirectionTo = nil
	t.Position = nil
	t.StationBoarding = nil
}

func (t *Train) IsRememberedBranchSameReal() bool {
	return t.RememberedBranch.IsSameWith(t.CurrentBranch) && t.RememberedBranch.RailRoad().HasBranch(t.CurrentBranch)
}

func (t *Train) IsOnBranch() bool {
	return t.CurrentBranch != nil
}

func (t *Train) IsOnStation() bool {
	return t.StationBoarding != nil
}

func (t *Train) GetRememberedBranch() *BranchRemembered {
	return t.RememberedBranch
}

func (t *Train) SetRelocatePlanning(trainPlan *TrainPlan) {
	t.RelocationPlan = trainPlan
}

func (t *Train) CancelRelocatePlanning() {
	if t.RelocationPlan != nil {
		t.RelocationPlan.Remove()
	}
	t.RelocationPlan = nil
}

func (t *Train) Relocate() {
	t.CurrentBranch = t.RelocationPlan.CurrentBranch
	t.RememberedBranch = t.RelocationPlan.ToBranch
	t.Position = t.RelocationPlan.Position
	t.DirectionTo = t.RelocationPlan.DirectionTo
	t.Speed = 0
	t.CancelRelocatePlanning()
}

func (t *Train) loadPassenger(dt float64) bool {
	if t.StationBoarding == nil {
		return false
	}
	stationPassengers := t.StationBoarding.Passengers()
	for i := 0; i < len(stationPassengers); i++ {
		s := t.closestStationWithShape(stationPassengers[i].Shape())
		if s != nil && s != t.StationBoarding { // && t.isDirectionForArriveStation(s) {
			if t.togglePassengerElapsed > 0 {
				t.togglePassengerElapsed -= dt
				return true
			}
			t.togglePassengerElapsed = t.togglePassengerDuration

			t.passengers = append(t.passengers, t.StationBoarding.GivePassenger(i))
			return true
		}
	}

	return false
}

func (t *Train) unloadPassenger(dt float64) bool {
	if t.StationBoarding == nil {
		return false
	}

	for i := 0; i < len(t.passengers); i++ {
		canUnload := t.passengers[i].Shape() == t.StationBoarding.Shape()
		if !canUnload && t.closestStationWithShapeBySelfRoad(t.passengers[i].Shape()) == nil {
			s := t.closestStationWithShapeByConnectedRoads(t.passengers[i].Shape())
			if s == nil || s == t.StationBoarding {
				canUnload = true
			}
		}
		if canUnload {
			if t.togglePassengerElapsed > 0 {
				t.togglePassengerElapsed -= dt
				return true
			}
			t.togglePassengerElapsed = t.togglePassengerDuration

			if t.StationBoarding.Shape() != t.passengers[i].Shape() {
				t.StationBoarding.AddPassenger(t.passengers[i])
			} else {
				t.passengerDelivered++
			}
			t.passengers = append(t.passengers[:i], t.passengers[i+1:]...)
			return true
		}
	}
	return false
}

func (t *Train) closestStationWithShape(shape StationType) *Station {
	s := t.closestStationWithShapeBySelfRoad(shape)
	if s != nil {
		return s
	}

	return t.closestStationWithShapeByConnectedRoads(shape)
}

func (t *Train) closestStationWithShapeBySelfRoad(shape StationType) *Station {
	if t.CurrentBranch == nil || t.CurrentBranch.RailRoad() == nil || t.StationBoarding == nil {
		return nil
	}
	rr := t.CurrentBranch.RailRoad()
	return rr.FindStationWithShape(shape)
	//path := rr.BuildPathToStationShapeFromBranch(t.StationBoarding, shape, t.CurrentBranch)
	//if len(path) == 0 {
	//	return nil
	//}
	//return path[len(path)-1]
}

func (t *Train) closestStationWithShapeByConnectedRoads(shape StationType) *Station {
	if t.CurrentBranch == nil || t.CurrentBranch.RailRoad() == nil || t.StationBoarding == nil {
		return nil
	}

	rr := t.CurrentBranch.RailRoad()
	s := rr.FindStationWithShape(shape)
	if s != nil {
		return s
	}
	connections := rr.ConnectionsWithStationShape(shape)
	for i := 0; i < len(connections); i++ {
		path := rr.BuildPathBetweenStationsFromBranch(t.StationBoarding, connections[i].Station, t.CurrentBranch)
		if len(path) > 0 {
			return connections[i].Station
		}
	}
	return nil
}

func (t *Train) isDirectionForArriveStation(s *Station) bool {
	if t.CurrentBranch == nil || t.CurrentBranch.RailRoad() == nil || s == nil || t.StationBoarding == nil {
		return false
	}

	rr := t.CurrentBranch.RailRoad()
	return len(rr.BuildPathBetweenStationsFromBranch(t.StationBoarding, s, t.CurrentBranch)) > 0
}

func (t *Train) ReportAboutPassengerDeliveredCount() int {
	x := t.passengerDelivered
	t.passengerDelivered = 0
	return x
}

func NewTrainPlan(train *Train) *TrainPlan {
	return &TrainPlan{
		Base: train,
	}
}

type TrainPlan struct {
	Position      *geom.Point
	Rotation      geom.Radian
	ToBranch      *BranchRemembered
	CurrentBranch *BranchCompleted
	CurrentGoal   *geom.Point
	DirectionTo   *Station
	IsPlaced      bool
	Base          *Train
	NeedToRemove  bool
}

func (t *TrainPlan) IsRememberedBranchSameReal() bool {
	return t.ToBranch.IsSameWith(t.CurrentBranch) && t.ToBranch.RailRoad().HasBranch(t.CurrentBranch)
}

func (t *TrainPlan) GetRememberedBranch() *BranchRemembered {
	return t.ToBranch
}

func (t *TrainPlan) IsOnBranch() bool {
	return t.ToBranch != nil
}

func (t *TrainPlan) IsOnStation() bool {
	return false
}

func (t *TrainPlan) ApplyToBranch(branch *BranchCompleted) {
	if branch == nil {
		t.ToBranch = nil
		t.CurrentGoal = nil
		t.CurrentBranch = nil
		t.DirectionTo = nil
		t.IsPlaced = false
		t.Rotation = 0
		return
	}

	t.ToBranch = NewRememberedBranch(branch)
	t.CurrentBranch = branch
	t.CurrentGoal = branch.From()
	if t.Position.DistanceTo(*branch.From()) < t.Position.DistanceTo(*branch.To()) {
		t.DirectionTo = branch.FromStation()
	} else {
		t.DirectionTo = branch.ToStation()
	}
	bendPoint := t.ToBranch.CalcBendPosition()
	t.CurrentGoal = chooseMovingGoal(t.Position, t.DirectionTo.Position(), &bendPoint)
	t.Rotation = t.Position.AngleTo(*t.CurrentGoal)
}

func (t *TrainPlan) Remove() {
	t.NeedToRemove = true
}
